﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.ComponentModel;
using LowLevelGraphics.ColorSpaces;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Normalization modes
    /// </summary>
    public enum Normalization_Mode
    {
        RGB,
        YCbCr,
        YPbPr,
        YUV,
        RGBUseRange,
        RGBSQrt,
        MINMAXRGB
    }

    //TODO: extend this for all color spaces
    /// <summary>
    /// Normalizes color, choose between one of different modes
    /// </summary>
    public class Normalize : BaseImageFilter
    {
        protected Normalization_Mode m_NormalizeMode = Normalization_Mode.RGBUseRange;

        public delegate Color NormalizeFunction(Color _color);

        protected int m_nThreshold = 45;
        
        /// <summary>
        /// Use RGB normalization mode, when no parameters are specified
        /// </summary>
        public Normalize()
            : this(Normalization_Mode.RGB)
        {

        }

        /// <summary>
        /// Specify normalization mode explicitly
        /// </summary>
        /// <param name="_normalizeMode"></param>
        public Normalize(Normalization_Mode _normalizeMode)
        {
            m_NormalizeMode = _normalizeMode;
        }

        /// <summary>
        /// used by clone to create a deep copy
        /// </summary>
        /// <param name="_normalize"></param>
        internal Normalize(Normalize _normalize)
        {
            this.m_NormalizeMode = _normalize.m_NormalizeMode;
            this.m_nThreshold = _normalize.m_nThreshold;
        }

        /// <summary>
        /// Gets the threshold.
        /// </summary>
        /// <value>The threshold.</value>
        public int Threshold
        {
            get { return m_nThreshold; }
            set { m_nThreshold = value; }
        }

        /// <summary>
        /// Gets or sets normalization mode
        /// </summary>
        public Normalization_Mode NormalizationMode
        {
            get { return m_NormalizeMode; }
            set { m_NormalizeMode = value; }
        }

        /// <summary>
        /// Normalizes a picture, i.e. all pixel colors are normalized.
        /// </summary>
        /// <param name="_bitmap"></param>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            NormalizeFunction function = null;

            float nRangeMin = 0;
            float nRangeMax = 255;

            switch (m_NormalizeMode)
            {
                case Normalization_Mode.MINMAXRGB:
                    NormalizeMinMaxRGB(_bitmap);
                    return _bitmap;

                case Normalization_Mode.RGB:
                    function = new NormalizeFunction(ColorFunctions.NormalizeToColor);
                    break;

                case Normalization_Mode.YCbCr:
                    function = new NormalizeFunction(NormalizeYCbCr);
                    break;

                case Normalization_Mode.YPbPr:
                    function = new NormalizeFunction(NormalizeYPbPr);
                    break;

                case Normalization_Mode.YUV:
                    function = new NormalizeFunction(NormalizeYuv);
                    break;

                case Normalization_Mode.RGBUseRange:
                    List<float> aSum = BitmapFunctions.ColorBrightnesses(_bitmap);
                    nRangeMin = aSum[0];
                    nRangeMax = aSum[aSum.Count - 1];
                    break;

                //Normalize and return result immediately
                case Normalization_Mode.RGBSQrt:
                    NormalizeUsingSQRT(_bitmap);
                    return _bitmap;
            }

            NormalizeDefault(_bitmap, function, nRangeMin, nRangeMax);
            return _bitmap;
        }

        /// <summary>
        /// Normalizes the default.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="function">The function.</param>
        /// <param name="nRangeMin">The n range min.</param>
        /// <param name="nRangeMax">The n range max.</param>
        private void NormalizeDefault(UnsafeBitmap _bitmap, NormalizeFunction function, float nRangeMin, float nRangeMax)
        {
            Color color = Color.Transparent;

            int nHeight = _bitmap.Height;
            int nWidth = _bitmap.Width;

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    color = _bitmap.GetPixel(x, y);

                    if (ColorFunctions.Gray(color) <= m_nThreshold)
                    {
                        continue;
                    }
                    if (m_NormalizeMode == Normalization_Mode.RGBUseRange)
                    {
                        color = ColorFunctions.Normalize(color, (int)nRangeMin, (int)nRangeMax).ToColor();
                    }
                    else
                    {
                        color = function(_bitmap.GetPixel(x, y));
                    }
                    _bitmap.SetPixel(x, y, color);
                }
            }
        }

        /// <summary>
        /// Normalize and detect automatic Min/Max R, G, B components
        /// </summary>
        /// <param name="_bitmap"></param>
        private void NormalizeMinMaxRGB(UnsafeBitmap _bitmap)
        {
            int R = 0;
            int G = 0;
            int B = 0;
            int RMin = 255;
            int GMin = 255;
            int BMin = 255;
            int RMax = 0;
            int GMax = 0;
            int BMax = 0;
            Color color = Color.Transparent;

            int nHeight = _bitmap.Height;
            int nWidth = _bitmap.Width;

            int x = 0;
            int y = 0;


            for (y = 0; y < nHeight; y++)
            {
                for (x = 0; x < nWidth; x++)
                {
                    color = _bitmap.GetPixel(x, y);
                    R = color.R;
                    G = color.G;
                    B = color.B;
                    if (R > RMax) RMax = R;
                    if (G > GMax) GMax = G;
                    if (B > BMax) BMax = B;

                    if (R < RMin) RMin = R;
                    if (G < GMin) GMin = G;
                    if (B < BMin) BMin = B;
                }
            }

            for (y = 0; y < nHeight; y++)
            {
                for (x = 0; x < nWidth; x++)
                {
                    color = _bitmap.GetPixel(x, y);
                    R = (int)(((float)(color.R - RMin) / (float)(RMax - RMin)) * 255.0f);
                    G = (int)(((float)(color.G - GMin) / (float)(GMax - GMin)) * 255.0f);
                    B = (int)(((float)(color.B - BMin) / (float)(BMax - BMin)) * 255.0f);
                    _bitmap.SetPixelDirect(x, y, (byte)R, (byte)G, (byte)B);
                }
            }
        }
        /// <summary>
        /// Normalizes the using SQRT.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        private void NormalizeUsingSQRT(UnsafeBitmap _bitmap)
        {
            float fFactor = 255;
            int nOffset = 0;

            List<float> aSort = new List<float>();

            int y = 0;
            int x = 0;

            for (y = 0; y < _bitmap.Height; y++)
            {
                for (x = 0; x < _bitmap.Width; x++)
                {
                    Color color = _bitmap.GetPixel(x, y);
                    double fSum = (float)Math.Sqrt(color.R * color.R + color.G * color.G + color.B * color.B) * 1.732050808 + 1;
                    double fR = (float)(color.R + 1) / (fSum);
                    double fG = (float)(color.G + 1) / (fSum);
                    double fB = (float)(color.B + 1) / (fSum);

                    fR *= fFactor + nOffset;
                    fG *= fFactor + nOffset;
                    fB *= fFactor + nOffset;

                    int nR = (int)fR; if (nR > 255) nR = 255;
                    int nG = (int)fG; if (nG > 255) nG = 255;
                    int nB = (int)fB; if (nB > 255) nB = 255;

                    if (ColorFunctions.Gray(color) > m_nThreshold)
                    {
                        if (Definitions.White != color && Definitions.Black != color)
                        {
                            _bitmap.SetPixel(x, y, Color.FromArgb(nR, nG, nB));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Use this for normalizing in YCbCr mode
        /// </summary>
        /// <param name="_color"></param>
        /// <returns></returns>
        private Color NormalizeYCbCr(Color _color)
        {
            AbstractColorSpace abstractColorSpace = new YCbCr(_color);
            return abstractColorSpace.NormalizeToRGBColor();
        }

        //TODO: 
        /// <summary>
        /// Use this to normalize in YPbPr mode
        /// </summary>
        /// <param name="_color"></param>
        /// <returns></returns>
        private Color NormalizeYPbPr(Color _color)
        {
            AbstractColorSpace abstractColorSpace = new YPbPr(_color);
            return abstractColorSpace.NormalizeToRGBColor();
        }

        //TODO
        /// <summary>
        /// Use this to normalize in Yuv-Mode
        /// </summary>
        /// <param name="_color"></param>
        /// <returns></returns>
        private Color NormalizeYuv(Color _color)
        {
            AbstractColorSpace abstractColorSpace = new YUV(_color);
            return abstractColorSpace.NormalizeToRGBColor();
        }

        /// <summary>
        /// Use this to create an excact copy of this filter
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Normalize(this);
        }
    }
}
